package com.excel.base;

import com.excel.ServerErrorException;
import com.excel.data.CheckExcelDTO;
import com.excel.utils.ExcelUtils;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.beans.BeanUtils;

import java.awt.Color;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.excel.utils.Cent.TWO;
import static com.excel.utils.DateTimeConstants.DATE_STR_SPILT;
import static com.excel.utils.DateTimeConstants.DATE_STR_SPILT_SLANT;
import static java.time.ZoneId.systemDefault;
import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;

/**
 * @author yutao
 * @since 2020/4/29 6:13 下午
 */
public interface IWmsUpload<T extends CheckExcelDTO> {

    /**
     * 上传库存excel
     *
     * @param workbook
     * @return
     * @throws ReflectiveOperationException
     */
    default List<T> upload(XSSFWorkbook workbook) throws ReflectiveOperationException {
        return upload(workbook, true);
    }

    /**
     * 上传库存excel
     *
     * @param workbook
     * @param checkFirstColumn 校验第一列标志
     * @return
     * @throws ReflectiveOperationException
     */
    @SuppressWarnings("checkstyle:methodlength")
    default List<T> upload(XSSFWorkbook workbook, boolean checkFirstColumn) throws ReflectiveOperationException {
        List<T> stocks = Lists.newArrayList();
        for (int i = 0, sheets = workbook.getNumberOfSheets(); i < sheets; i++) {
            XSSFSheet childSheet = workbook.getSheetAt(i);
            for (int r = 1, rows = childSheet.getPhysicalNumberOfRows(); r < rows; r++) {
                XSSFRow row = childSheet.getRow(r);
                if (checkFirstColumn) {
                    if (row == null || row.getCell(0) == null) {
                        continue;
                    }
                    row.getCell(0).setCellType(CellType.STRING);
                    if (StringUtils.isBlank(row.getCell(0).getStringCellValue())) {
                        continue;
                    }
                }
                T stock = createStock();
                for (int c = 0; c < getUploadFieldSize(); c++) {
                    String propertyName = getPropertyName(c);
                    Cell cell = row.getCell(c);
                    if (cell == null || StringUtils.isBlank(propertyName)) {
                        continue;
                    }
                    String value;
                    if (CellType.NUMERIC.equals(cell.getCellTypeEnum())) {
                        value = ExcelUtils.doubleValueToString(cell.getNumericCellValue());
                    } else {
                        cell.setCellType(CellType.STRING);
                        value = row.getCell(c).getStringCellValue().trim();
                    }
                    if (StringUtils.isBlank(value)) {
                        continue;
                    }
                    Method method = BeanUtils.getPropertyDescriptor(stock.getClass(), propertyName).getWriteMethod();
                    Class type = method.getParameterTypes()[0];
                    if (type.isEnum()) {
                        handleEnumType(method, stock, type, value);
                        continue;
                    }
                    switch (type.getSimpleName()) {
                        case "String":
                            handleStringType(method, stock, propertyName, value);
                            break;
                        case "Long":
                            handleLongType(method, stock, propertyName, value);
                            break;
                        case "Integer":
                            handleIntegerType(method, stock, propertyName, value);
                            break;
                        case "Short":
                            handleShortType(method, stock, propertyName, value);
                            break;
                        case "BigDecimal":
                            handleBigDecimalType(method, stock, propertyName, value);
                            break;
                        case "List":
                            handleListType(method, stock, propertyName, value);
                            break;
                        case "Boolean":
                            handleBooleanType(method, stock, propertyName, value);
                            break;
                        case "OffsetDateTime":
                            handleOffsetDateTimeType(method, stock, propertyName, value);
                            break;
                        case "Byte":
                            method.invoke(stock, Byte.valueOf(Objects.requireNonNull(value)));
                            break;
                        default:
                            throw new ServerErrorException("不支持的字段类型");
                    }
                }
                postStockInitialization(row, stock);
                stocks.add(stock);
            }
        }
        return stocks;
    }

    /**
     * 为导出错误提示的信息做准备
     *
     * @param workbook
     * @param checkExcelRowDTO
     * @return
     */
    @SuppressWarnings({"checkstyle:methodlength", "PMD.MethodTooLongRule"})
    default boolean checkErrorAndWriteWorkbook(XSSFWorkbook workbook, List<T> checkExcelRowDTO) {
        // 提取有错误的对象
        List<T> errorCheckRowDTOList = checkExcelRowDTO.stream()
                .filter(CheckExcelDTO::isHappenedError).collect(Collectors.toList());

        return Optional.of(errorCheckRowDTOList).filter(CollectionUtils::isNotEmpty)
                .map(errorCheckRowDTOs -> {
                    int length = getUploadFieldSize();
                    errorCheckRowDTOs.stream().map(CheckExcelDTO::getSheetId).distinct().forEach(sheetId -> {
                        XSSFSheet sheet = workbook.getSheetAt(sheetId);
                        for (int i = 0; i < TWO; i++) {
                            XSSFRow titleRow = sheet.getRow(i);
                            if (titleRow == null) {
                                titleRow = sheet.createRow(i);
                            }
                            XSSFCell errorTitleCell = titleRow.createCell(length);
                            errorTitleCell.setCellType(CellType.STRING);
                            // 设置样式
                            XSSFCellStyle errorTitleStyle = workbook.createCellStyle();
                            errorTitleStyle.cloneStyleFrom(errorTitleCell.getCellStyle());
                            errorTitleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                            errorTitleStyle.setAlignment(HorizontalAlignment.CENTER);
                            errorTitleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
                            errorTitleStyle.setFillForegroundColor(new XSSFColor(java.awt.Color.YELLOW));
                            errorTitleStyle.setBorderBottom(BorderStyle.THIN);
                            // 设置字体
                            XSSFFont font = workbook.createFont();
                            font.setFontName(errorTitleCell.getCellStyle().getFont().getFontName());
                            font.setColor(new XSSFColor(java.awt.Color.RED));
                            font.setBold(true);
                            errorTitleStyle.setFont(font);
                            errorTitleCell.setCellStyle(errorTitleStyle);
                            errorTitleCell.setCellValue("报错提示");
                        }
                    });
                    // 设置内容的样式
                    XSSFCellStyle errorMsgStyle = workbook.createCellStyle();
                    errorMsgStyle.setBorderBottom(BorderStyle.THIN);
                    errorMsgStyle.setAlignment(HorizontalAlignment.CENTER);

                    errorCheckRowDTOs.forEach(errorCheck -> {
                        // 定位到具体的那一行，然后把错误信息写在该行的最后面
                        XSSFRow row = workbook.getSheetAt(errorCheck.getSheetId()).getRow(errorCheck.getRowId());
                        // 这里的cellId对应的相应字段
                        errorCheck.getErrorCellIds().forEach(cellId -> {
                            XSSFCell errorDataCell = row.getCell(cellId);
                            XSSFCellStyle errorDataStyle = workbook.createCellStyle();
                            errorDataStyle.cloneStyleFrom(errorDataCell.getCellStyle());
                            errorDataStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                            errorDataStyle.setFillForegroundColor(new XSSFColor(Color.YELLOW));
                            errorDataCell.setCellStyle(errorDataStyle);
                        });
                        // 自动设置列宽
                        workbook.getSheetAt(errorCheck.getSheetId()).autoSizeColumn(length);
                        XSSFCell errorMsgCell = row.createCell(length);
                        errorMsgCell.setCellStyle(errorMsgStyle);
                        errorMsgCell.setCellType(CellType.STRING);
                        errorMsgCell.setCellValue(errorCheck.getErrorMessage());
                    });
                    return true;
                }).orElse(false);
    }

    /**
     * handleOffsetDateTimeType
     *
     * @param method
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleOffsetDateTimeType(Method method, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        value = Optional.ofNullable(value).filter(f -> f.contains(DATE_STR_SPILT_SLANT))
                .map(m -> m.replaceAll(DATE_STR_SPILT_SLANT, DATE_STR_SPILT)).orElse(value);

        assert value != null;
        OffsetDateTime offsetDateTime = LocalDate.parse(value, ISO_LOCAL_DATE).atTime(LocalTime.MIN)
                .atZone(systemDefault()).toOffsetDateTime();
        method.invoke(stock, offsetDateTime);
    }

    public static void main(String[] args) {

        String value = "2020/01/03";

        value = Optional.ofNullable(value).filter(f -> f.contains(DATE_STR_SPILT_SLANT))
                .map(m -> m.replaceAll(DATE_STR_SPILT_SLANT, DATE_STR_SPILT)).orElse(value);

        OffsetDateTime offsetDateTime = LocalDate.parse(value, ISO_LOCAL_DATE).atTime(LocalTime.MIN)
                .atZone(systemDefault()).toOffsetDateTime();
        System.out.println(offsetDateTime.toLocalDateTime());
    }

    /**
     * 对顶级表头单独处理
     *
     * @param topHeadField
     * @param childSheet
     * @param t
     */
    default void setHeaderRows(String[] topHeadField, XSSFSheet childSheet, T t) {
        XSSFRow rowTop = childSheet.getRow(0);
        for (int i = 1, length = topHeadField.length, j = 0; j < length; i = i + TWO, j++) {
            Cell cellTop = rowTop.getCell(i);
            String cellValue = cellTop.getStringCellValue();
            if (StringUtils.isNotBlank(cellValue)) {
                try {
                    String valueTop = cellValue.trim();
                    String topHeader = topHeadField[j];
                    Method method = BeanUtils.getPropertyDescriptor(t.getClass(), topHeader)
                            .getWriteMethod();
                    Class type = method.getParameterTypes()[0];
                    switch (type.getSimpleName()) {
                        case "String":
                            method.invoke(t, valueTop);
                            break;
                        case "Integer":
                            method.invoke(t, Integer.valueOf(valueTop));
                            break;
                        case "Boolean":
                            method.invoke(t, "是".equalsIgnoreCase(valueTop));
                            break;
                        default:
                            throw new ServerErrorException("不支持的字段类型");
                    }
                } catch (ReflectiveOperationException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取上传属性数量
     *
     * @return
     */
    Integer getUploadFieldSize();

    /**
     * getTopHeadFiled
     *
     * @return
     */
    String[] getTopHeadFiled();

    /**
     * 获取属性名
     *
     * @param propertyIndex
     * @return
     */
    String getPropertyName(int propertyIndex);

    /**
     * 创建stock
     *
     * @return
     */
    T createStock();

    /**
     * 初始化stock
     *
     * @param row
     * @param stock
     */
    void postStockInitialization(XSSFRow row, T stock);

    /**
     * 处理String类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleStringType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        setter.invoke(stock, value);
    }

    /**
     * 处理Long类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleLongType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        setter.invoke(stock, Long.valueOf(value));
    }

    /**
     * 处理Integer类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleIntegerType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        setter.invoke(stock, Integer.valueOf(value));
    }

    /**
     * 处理Short类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleShortType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        setter.invoke(stock, Short.valueOf(value));
    }

    /**
     * 处理BigDecimal类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleBigDecimalType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        try {
            setter.invoke(stock, new BigDecimal(value));
        } catch (Exception e) {
        }
    }

    /**
     * 处理List类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleListType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
    }

    /**
     * 处理Boolean类型
     *
     * @param setter
     * @param stock
     * @param propertyName
     * @param value
     * @throws ReflectiveOperationException
     */
    @SuppressWarnings("PMD.UndefineMagicConstantRule")
    default void handleBooleanType(Method setter, T stock, String propertyName, String value)
            throws ReflectiveOperationException {
        if (StringUtils.equals(StringUtils.trim(value), "是")) {
            setter.invoke(stock, true);
        } else {
            setter.invoke(stock, false);
        }
    }

    /**
     * 处理Enum类型
     *
     * @param setter
     * @param stock
     * @param enumClz
     * @param value
     * @throws ReflectiveOperationException
     */
    default void handleEnumType(Method setter, T stock, Class enumClz, String value)
            throws ReflectiveOperationException {
        Method byName = enumClz.getMethod("byName", String.class);
        // 因为是静态方法，所以第一个参数不用传实例对象
        Object enumValue = byName.invoke(null, value);
        if (enumValue == null) {
            throw new ServerErrorException("枚举名不合法：" + enumClz.getSimpleName());
        }
        setter.invoke(stock, enumValue);
    }
}
